package com.lemon.exam.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.lemon.exam.common.crud.BaseServiceImpl;
import com.lemon.exam.common.entity.system.LoginUser;
import com.lemon.exam.common.entity.view.ViewUserRoleMenuResource;
import com.lemon.exam.common.entity.vo.SelectVO;
import com.lemon.exam.common.entity.vo.TreeSelectVO;
import com.lemon.exam.common.enums.StatusEnum;
import com.lemon.exam.common.exception.CustomException;
import com.lemon.exam.common.util.MailUtil;
import com.lemon.exam.common.util.RandomUtil;
import com.lemon.exam.common.util.SecurityUtil;
import com.lemon.exam.common.web.Param;
import com.lemon.exam.entity.po.*;
import com.lemon.exam.entity.vo.RoleClassJobVO;
import com.lemon.exam.entity.vo.UserVO;
import com.lemon.exam.repository.UserRepository;
import com.lemon.exam.service.IClassUserService;
import com.lemon.exam.service.IUserRoleService;
import com.lemon.exam.service.IUserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.relational.core.query.Criteria;
import org.springframework.data.relational.core.query.Query;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuple2;
import reactor.util.retry.Retry;
import reactor.util.retry.RetryBackoffSpec;

import java.time.Duration;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.lemon.exam.common.constant.ApiConst.INSERT_SUCCESS;
import static com.lemon.exam.common.constant.ApiConst.UPDATE_SUCCESS;
import static com.lemon.exam.common.enums.CodeEnum.ERROR_402;

/**
 * 用户表 服务实现类
 *
 * @author Lemon
 * @since 2025/03/24 10:14:39
 */
@Slf4j
@Service
public class UserServiceImpl extends BaseServiceImpl<UserPO, Long, UserRepository> implements IUserService, ReactiveUserDetailsService {
    @Resource
    private IUserRoleService userRoleService;
    @Resource
    private IClassUserService classUserService;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private MailUtil mailUtil;

    @Override
    public Mono<UserDetails> findByUsername(String username) {
        return repository.findByUsername(username)
                .flatMap(user -> repository.findPathByUserId(user.getId()).collectList().map(getLoginUser(user)));
    }

    /**
     * 数据转换
     */
    private static Function<List<ViewUserRoleMenuResource>, LoginUser> getLoginUser(UserPO user) {
        return list -> {
            LoginUser loginUser = new LoginUser();
            loginUser.setId(user.getId());
            loginUser.setUsername(user.getUsername());
            loginUser.setName(user.getName());
            loginUser.setPassword(user.getPassword());
            loginUser.setEnabled(StatusEnum.ENABLE.getValue().equals(user.getStatus()));
            List<String> roleList = list.stream().map(ViewUserRoleMenuResource::getRoleKey).filter(StringUtils::hasText).distinct().toList();
            if (!CollectionUtils.isEmpty(roleList)) {
                loginUser.setRoleList(roleList);
            }
            List<String> resourceList = list.stream()
                    .filter(item -> StringUtils.hasText(item.getMenuPath()) && StringUtils.hasText(item.getResourcePath()) && Objects.equals(item.getMenuStatus(), 1))
                    .map(item -> item.getMenuPath() + item.getResourcePath())
                    .distinct().toList();
            if (!CollectionUtils.isEmpty(resourceList)) {
                loginUser.setResourceList(resourceList);
            }
            return loginUser;
        };
    }

    @Override
    public Mono<?> findById(Long id) {
        return Mono.zip(
                        repository.findUserById(id).subscribeOn(Schedulers.boundedElastic()),
                        this.getRoleIdList(id),
                        this.getGradeIdClassIdJobIdList(id)
                )
                .map(tuple -> {
                    UserVO user = tuple.getT1();
                    user.setRoleIdList(tuple.getT2());
                    user.setGradeClassJobIdList(tuple.getT3());
                    return user;
                });
    }

    /**
     * 根据用户查询角色ID
     *
     * @param userId
     * @return
     */
    private Mono<List<Long>> getRoleIdList(Long userId) {
        return databaseClient.sql("""
                        SELECT id FROM sys_role WHERE status = 1 AND deleted = 0 AND id IN
                        (SELECT role_id FROM sys_user_role WHERE user_id = :userId)
                        """)
                .bind("userId", userId)
                .map(row -> row.get("id", Long.class))
                .all()
                .collectList()
                .subscribeOn(Schedulers.boundedElastic());
    }

    /**
     * 根据用户查询班级ID、年级ID、职位ID
     *
     * @param userId
     * @return
     */
    private Mono<List<List<Long>>> getGradeIdClassIdJobIdList(Long userId) {
        return databaseClient.sql("""
                        SELECT
                        	g.id AS grade_id,
                        	c.id AS class_id,
                        	j.id AS job_id
                        FROM
                        	biz_class_user cu
                        	LEFT JOIN biz_class c ON ( c.id = cu.class_id AND c.deleted = 0 )
                        	LEFT JOIN biz_grade g ON ( g.id = c.grade_id AND g.deleted = 0 )
                        	LEFT JOIN biz_job j ON ( j.id = cu.job_id AND j.deleted = 0 )
                        WHERE
                        	cu.user_id = :userId
                        """)
                .bind("userId", userId)
                .map(row -> {
                    List<Long> list = new ArrayList<>();
                    list.add(row.get("grade_id", Long.class));
                    list.add(row.get("class_id", Long.class));
                    Long jobId = row.get("job_id", Long.class);
                    if (jobId != null) {
                        list.add(jobId);
                    }
                    return list;
                })
                .all()
                .collectList()
                .subscribeOn(Schedulers.boundedElastic());
    }

    @Override
    public Mono<Page<?>> page(Param param) {
        return Mono.zip(param.getCriteria(), this.logicDeleteCriteria())
                .map(tuple -> tuple.getT1().and(tuple.getT2()))
                .zipWith(param.getPageRequest())
                .flatMap(tuple -> Mono.zip(
                        template.select(Query.query(tuple.getT1()).with(tuple.getT2()), this.entityClass).collectList(),
                        template.count(Query.query(tuple.getT1()), this.entityClass)
                ).flatMap(toPage(tuple.getT2())));
    }

    /**
     * 转换为分页数据
     *
     * @param pageRequest
     * @return
     */
    private Function<Tuple2<List<UserPO>, Long>, Mono<? extends PageImpl<?>>> toPage(PageRequest pageRequest) {
        return tuple -> {
            if (CollectionUtils.isEmpty(tuple.getT1())) {
                return Mono.just(new PageImpl<>(new ArrayList<>(0), pageRequest, tuple.getT2()));
            }
            return toPage(
                    pageRequest,
                    tuple.getT1(),
                    tuple.getT2(),
                    tuple.getT1().stream().map(UserPO::getId).toList()
            );
        };
    }

    /**
     * 转换为分页数据
     *
     * @param pageRequest
     * @param userList
     * @param count
     * @param userIdList
     * @return
     */
    private Mono<PageImpl<UserVO>> toPage(PageRequest pageRequest, List<UserPO> userList, Long count, List<Long> userIdList) {
        return Mono.zip(
                convertToUserVO(userList).subscribeOn(Schedulers.parallel()),
                this.findRoleNameByUserIdList(userIdList).subscribeOn(Schedulers.parallel()),
                this.getGradeNameClassNameJobName(userIdList).subscribeOn(Schedulers.parallel())
        ).map(tuple -> {
            List<UserVO> users = tuple.getT1();
            users.forEach(user -> {
                user.setRoleNameList(tuple.getT2().getOrDefault(user.getId(), Collections.emptyList()));
                user.setGradeClassJobNameList(tuple.getT3().getOrDefault(user.getId(), Collections.emptyList()));
            });
            return new PageImpl<>(new ArrayList<>(users), pageRequest, count);
        });
    }

    /**
     * 查询用户角色名称
     *
     * @param userIdList
     * @return
     */
    private Mono<Map<Long, List<String>>> findRoleNameByUserIdList(List<Long> userIdList) {
        return databaseClient.sql("""
                        SELECT
                        	ur.user_id,
                        	r.name
                        FROM
                        	sys_role r
                        	JOIN sys_user_role ur ON r.id = ur.role_id
                        WHERE
                        	r.status = 1
                        	AND r.deleted = 0
                        	AND ur.user_id IN (:userIdList)
                        """)
                .bind("userIdList", userIdList)
                .fetch()
                .all()
                .collectMultimap(
                        row -> Long.parseLong(String.valueOf(row.get("user_id"))),
                        row -> String.valueOf(row.get("name"))
                )
                .map(map -> map.entrySet().stream().collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> new ArrayList<>(entry.getValue())
                )));
    }

    /**
     * 根据用户ID查询班级名称、年级名称、职位名称
     *
     * @param userIdList
     * @return
     */
    private Mono<Map<Long, List<String>>> getGradeNameClassNameJobName(List<Long> userIdList) {
        return databaseClient.sql("""
                        SELECT
                        	cu.user_id,
                        	CONCAT(g.name,'/',c.name,'/',IFNULL(j.name,'')) AS name
                        FROM
                        	biz_class_user cu
                        	LEFT JOIN biz_class c ON ( c.id = cu.class_id AND c.deleted = 0 )
                        	LEFT JOIN biz_grade g ON ( g.id = c.grade_id AND g.deleted = 0 )
                        	LEFT JOIN biz_job j ON ( j.id = cu.job_id AND j.deleted = 0 )
                        WHERE
                        	cu.user_id IN (:userId)
                        """)
                .bind("userId", userIdList)
                .fetch()
                .all()
                .collectMultimap(
                        row -> Long.parseLong(row.get("user_id").toString()),  // 分组key
                        row -> {
                            String name = String.valueOf(row.get("name"));
                            return name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
                        }
                ).map(multiMap -> multiMap.entrySet().stream().collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> new ArrayList<>(entry.getValue())
                )));
    }

    /**
     * 转换为UserVO
     *
     * @param userList
     * @return
     */
    private Mono<List<UserVO>> convertToUserVO(List<UserPO> userList) {
        return Flux.fromIterable(userList).map(user -> {
                    UserVO vo = objectMapper.convertValue(user, UserVO.class);
                    if (StringUtils.hasText(vo.getPrevLoginIp())) {
                        vo.setPrevLoginIp(vo.getPrevLoginIp().split(",")[0]);
                    }
                    if (StringUtils.hasText(vo.getPrevLoginTime())) {
                        vo.setPrevLoginTime(vo.getPrevLoginTime().split(",")[0]);
                    }
                    String phone = vo.getPhone();
                    if (StringUtils.hasText(phone)) {
                        vo.setPhone(phone.substring(0, 3) + "****" + phone.substring(7));
                    }
                    return vo;
                }).collectList()
                .subscribeOn(Schedulers.parallel());
    }

    @Override
    public Mono<RoleClassJobVO> findSelect() {
        return this.getClassList().collectList()
                .flatMap(classList -> Mono.zip(
                        this.getRoleSelect().collectList(),
                        this.getGrade(classList.stream().map(ClassPO::getGradeId).toList()).collectList(),
                        this.getJobSelect().collectList()
                ).map(tuple -> new RoleClassJobVO(
                        tuple.getT1(),
                        buildTreeData(classList, tuple.getT2(), tuple.getT3())
                )));
    }

    /**
     * 构建树结构数据
     *
     * @param classList
     * @param gradeList
     * @param treeSelect
     * @return
     */
    private List<TreeSelectVO> buildTreeData(List<ClassPO> classList, List<GradePO> gradeList, List<TreeSelectVO> treeSelect) {
        Map<Long, List<TreeSelectVO>> groupTreeSelect = classList.stream().collect(Collectors.groupingBy(ClassPO::getGradeId))
                .entrySet().stream().collect(Collectors.toMap(
                        Map.Entry::getKey,
                        entry -> entry.getValue().stream().map(item -> new TreeSelectVO(item.getId(), item.getName(), treeSelect)).toList())
                );
        return gradeList.stream()
                .map(item -> new TreeSelectVO(
                        item.getId(),
                        item.getName(),
                        groupTreeSelect.get(item.getId()))
                ).toList();
    }

    /**
     * 获取角色下拉框数据
     *
     * @return
     */
    private Flux<SelectVO> getRoleSelect() {
        return databaseClient.sql("SELECT id, name FROM sys_role WHERE status = 1 AND deleted = 0")
                .map(row -> new SelectVO(String.valueOf(row.get("id", Long.class)), row.get("name", String.class)))
                .all()
                .subscribeOn(Schedulers.parallel());
    }

    /**
     * 获取职位下拉框数据
     *
     * @return
     */
    private Flux<TreeSelectVO> getJobSelect() {
        return databaseClient.sql("SELECT id, name FROM biz_job WHERE deleted = 0")
                .map(row -> new TreeSelectVO(row.get("id", Long.class), row.get("name", String.class), null))
                .all()
                .subscribeOn(Schedulers.parallel());
    }

    /**
     * 获取班级数据
     *
     * @return
     */
    private Flux<ClassPO> getClassList() {
        return databaseClient.sql("SELECT id, grade_id, name FROM biz_class WHERE deleted = 0")
                .map(row -> {
                    ClassPO po = new ClassPO();
                    po.setId(row.get("id", Long.class));
                    po.setGradeId(row.get("grade_id", Long.class));
                    po.setName(row.get("name", String.class));
                    return po;
                })
                .all();
    }

    /**
     * 获取年级数据
     *
     * @param gradeIdList
     * @return
     */
    private Flux<GradePO> getGrade(List<Long> gradeIdList) {
        return databaseClient.sql("SELECT id, name FROM biz_grade WHERE deleted = 0 AND id IN (:gradeIdList)")
                .bind("gradeIdList", gradeIdList)
                .map(row -> {
                    GradePO grade = new GradePO();
                    grade.setId(row.get("id", Long.class));
                    grade.setName(row.get("name", String.class));
                    return grade;
                })
                .all()
                .subscribeOn(Schedulers.parallel());
    }

    @Override
    public Mono<String> insert(UserPO param) {
        return Mono.zip(
                SecurityUtil.getUsername(),
                super.exist("username", param.getUsername()),
                super.exist("phone", param.getPhone()),
                super.exist("email", param.getEmail())
        ).flatMap(tuple -> {
            if (tuple.getT2()) {
                return Mono.error(new CustomException(402, "用户名已存在！"));
            } else if (tuple.getT3()) {
                return Mono.error(new CustomException(402, "手机号已存在！"));
            } else if (tuple.getT4()) {
                return Mono.error(new CustomException(402, "邮箱已存在！"));
            } else {
                //生成密码
                String password = RandomUtil.all(6);
                param.setPassword(SecurityUtil.passwordEncode(password));
                param.setCreateBy(tuple.getT1());
                return template.insert(param).map(UserPO::getId)
                        .flatMap(userId -> insertUserRoleClassJob(param, userId, password)
                                .thenReturn(INSERT_SUCCESS)
                                .as(transactionalOperator::transactional)
                        ).onErrorResume(err -> Mono.error(new CustomException(ERROR_402)));
            }
        });
    }

    private Mono<Boolean> insertUserRoleClassJob(UserPO param, Long userId, String password) {
        // 构建班级用户关联列表
        List<ClassUserPO> classUserList = buildClassUserList(param.getGradeClassJobIdList(), userId);
        // 构建用户角色关联列表
        List<UserRolePO> userRoleList = buildUserRoleList(param.getRoleIdList(), userId);
        return Mono.zip(
                        userRoleService.saveAll(userRoleList).subscribeOn(Schedulers.parallel()),
                        classUserService.saveAll(classUserList).subscribeOn(Schedulers.parallel()),
                        mailUtil.send(param.getEmail(), "用户注册成功", "用户名：" + param.getUsername() + "，密码：" + password, false)
                                .thenReturn(true)
                                .onErrorResume(err -> Mono.just(true))// 发送邮件失败时，继续执行后续操作
                ).flatMap(tuple -> Mono.just(tuple.getT1() && tuple.getT2()))
                .onErrorResume(err -> Mono.error(new CustomException(ERROR_402)));
    }

    /**
     * 构建班级用户关联列表
     *
     * @param gradeClassJobIdList
     * @param userId
     * @return
     */
    private List<ClassUserPO> buildClassUserList(List<List<Long>> gradeClassJobIdList, Long userId) {
        return gradeClassJobIdList.stream()
                .peek(idList -> {
                    if (idList.size() < 2) { // 确保包含gradeId, classId, jobId（非必选）
                        throw new CustomException(402, "请选择完整的班级&职位信息");
                    }
                })
                .map(idList -> {
                    ClassUserPO po = new ClassUserPO().setUserId(userId).setClassId(idList.get(1));
                    if (idList.size() == 3) {
                        po.setJobId(idList.get(2));
                    }
                    return po;
                })
                .toList();
    }

    /**
     * 构建用户角色关联列表
     *
     * @param roleIdList
     * @param userId
     * @return
     */
    private List<UserRolePO> buildUserRoleList(List<Long> roleIdList, Long userId) {
        return roleIdList.stream()
                .map(roleId -> new UserRolePO().setRoleId(roleId).setUserId(userId))
                .toList();
    }

    // 重试配置
    private static final RetryBackoffSpec RETRY_SPEC = Retry
            .backoff(3, Duration.ofMillis(100))
            .maxBackoff(Duration.ofSeconds(1))
            .jitter(0.5)
            .doBeforeRetry(s -> log.warn("正在重试用户修改操作（尝试{}）", s.totalRetries()));

    @Override
    public Mono<String> update(UserPO param) {
        return Mono.zip(
                SecurityUtil.getUsername(),
                super.exist("username", param.getUsername(), param.getId()),
                super.exist("phone", param.getPhone(), param.getId()),
                super.exist("email", param.getEmail(), param.getId()),
                super.convertUpdateParam(param)
        ).flatMap(tuple -> {
            if (tuple.getT2()) {
                return Mono.error(new CustomException(402, "用户名已存在！"));
            } else if (tuple.getT3()) {
                return Mono.error(new CustomException(402, "手机号已存在！"));
            } else if (tuple.getT4()) {
                return Mono.error(new CustomException(402, "邮箱已存在！"));
            } else {
                return template.update(entityClass)
                        .matching(Query.query(Criteria.where(this.primaryKey).is(param.getId())))
                        .apply(tuple.getT5().set("update_by", tuple.getT1()))
                        .then(updateUserRoleClassJob(param, param.getId()))
                        .thenReturn(UPDATE_SUCCESS)
                        .as(transactionalOperator::transactional)
                        // 添加重试机制
                        .retryWhen(RETRY_SPEC)
                        .onErrorResume(err -> Mono.error(new CustomException(ERROR_402)));
            }
        });
    }

    /**
     * 更新用户角色关联
     *
     * @param param
     * @param userId
     * @return
     */
    private Mono<Boolean> updateUserRoleClassJob(UserPO param, Long userId) {
        // 构建班级用户关联列表
        List<ClassUserPO> classUserList = buildClassUserList(param.getGradeClassJobIdList(), userId);
        // 构建用户角色关联列表
        List<UserRolePO> userRoleList = buildUserRoleList(param.getRoleIdList(), userId);
        return Mono.zip(
                        userRoleService.deleteByUserId(userId),
                        classUserService.deleteByUserId(userId)
                ).then(Mono.zip(
                        userRoleService.saveAll(userRoleList),
                        classUserService.saveAll(classUserList)
                ))
                .flatMap(tuple -> Mono.just(tuple.getT1() && tuple.getT2()))
                .as(transactionalOperator::transactional)  //为关联操作使用单独事务
                .timeout(Duration.ofSeconds(5))  // 添加超时
                .onErrorResume(err -> {
                    log.error("更新用户角色班级关系失败: {}", err.getMessage());
                    return Mono.just(false);
                });
    }

    @Override
    public Flux<UserVO> findUserByRoleId(Long roleId) {
        return repository.findUserByRoleId(roleId);
    }

    @Override
    public Mono<Map<Long, String>> findUserIdAndUserNameByClassId(Long classId) {
        return repository.findUserIdAndUserNameByClassId(classId)
                .collectMap(UserPO::getId, UserPO::getName)
                .defaultIfEmpty(Collections.emptyMap());
    }
}
